﻿#region Copyright & licence

// This file is part of Refix.
// 
// Refix is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// Refix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with Refix.  If not, see <http://www.gnu.org/licenses/>.
// 
// Copyright (C) 2010-11 David Musgrove and others.

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

using Refix.Core.IO;
using Refix.Core.Properties;
using Refix.Core.Repository;

namespace Refix.Core.Structure
{
    public class SolutionHelper
    {
        private readonly LocalRepository _localRepository;

        public SolutionHelper(LocalRepository localRepository)
        {
            _localRepository = localRepository;
        }

        public Solution GetSolution(string fileName)
        {
            Solution solution;
            if (!File.Exists(fileName))
            {
                OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("SOLUTION_FILE_NOT_FOUND"), fileName);
                return null;
            }
            var solutionFile = new SolutionFile(fileName);
            VerifyDependencyFilesExist(solutionFile);
            if (!ConstructSolutionAndVerifyDependenciesAgainstRepository(solutionFile, out solution))
            {
                return null;
            }
            return solution;
        }

        static private void ExtractDependencyFiles(SolutionFile solutionFile)
        {
            foreach (ProjectFile projectFile in solutionFile.CachedProjectFiles.Values)
            {
                projectFile.WriteDependencyFile();
                OutputWriter.WriteLine(OutputVerbosity.Verbose, ResourceDispenser.GetString("EXTRACT_FILE_GENERATED"),
                                   Path.GetFileName(projectFile.FileName) + FileExtensions.DependencyFile);
            }
        }

        private bool ConstructSolutionAndVerifyDependenciesAgainstRepository(SolutionFile solutionFile,
                                                                             out Solution solution)
        {
            solution = new Solution {FileName = solutionFile.FileName};
            foreach (ProjectFile projectFile in solutionFile.CachedProjectFiles.Values)
            {
                var project = new Project {Name = projectFile.FileName};
                ProjectFile dependencyFile = projectFile.GetDependencyFile();
                if (!(VerifyAssembliesExist(dependencyFile, project.References)
                    && VerifyAssemblyVersionsExist(project.References)
                    && AddBinaryReferences(project.References)))
                {
                    return false;
                }
                solution.Projects.Add(project);
            }

            AssignHighestVersionNumbersToUnversionedReferences(solutionFile, solution);

            foreach (ProjectFile projectFile in solutionFile.CachedProjectFiles.Values)
            {
                ProjectFile file = projectFile;
                Project project = solution.Projects.Single(p => p.Name == file.FileName);
                ProjectFile dependencyFile = projectFile.GetDependencyFile();
                if (!AddProjectReferences(dependencyFile, solution, project.ProjectReferences))
                {
                    return false;
                }
            }
            return true;
        }

        static private void AssignHighestVersionNumbersToUnversionedReferences(SolutionFile solutionFile, Solution solution)
        {
            var versionedBinaries = (from project in solution.Projects
                                     from reference in project.References
                                     where !reference.OriginalVersion.IsNullOrEmpty()
                                     select reference.Name).Distinct()
                .ToDictionary(name => name, name => new List<string>());
            var keys = versionedBinaries.Keys.ToList();
            foreach (var key in keys)
            {
                string name = key;
                versionedBinaries[key] = (from project in solution.Projects
                                          from reference in project.References
                                          where !reference.OriginalVersion.IsNullOrEmpty()
                                          where reference.Name == name
                                          select reference.OriginalVersion).Distinct()
                    .ToList()
                    .OrderByDescending(version => version, new VersionNumberComparer())
                    .ToList();
            }
            foreach (var project in solution.Projects)
            {
                foreach (var binaryReference in project.References)
                {
                    if (!binaryReference.ResolvedVersion.IsNullOrEmpty())
                    {
                        continue;
                    }
                    if (!versionedBinaries.ContainsKey(binaryReference.Name))
                    {
                        continue;
                    }
                    binaryReference.ResolvedVersion = versionedBinaries[binaryReference.Name].First();
                }
            }
            foreach (var project in solutionFile.CachedProjectFiles.Values)
            {
                foreach (var binaryReference in project.References)
                {
                    if (!binaryReference.ResolvedVersion.IsNullOrEmpty())
                    {
                        continue;
                    }
                    if (!versionedBinaries.ContainsKey(binaryReference.AssemblyName))
                    {
                        continue;
                    }
                    binaryReference.ResolvedVersion = versionedBinaries[binaryReference.AssemblyName].First();
                }
            }
        }

        static private bool AddProjectReferences(ProjectFile dependencyFile, Solution solution, List<Project> projectReferences)
        {
            foreach (ProjectReference projectReference in dependencyFile.ProjectReferences)
            {
                ProjectReference reference = projectReference;
                Project project =
                    solution.Projects.SingleOrDefault(
                        p => Path.GetFileNameWithoutExtension(p.Name) == Path.GetFileNameWithoutExtension(reference.Path));
                if (project == null)
                {
                    OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("PROJECT_REFERENCE_NOT_FOUND"),
                                       Path.GetFileNameWithoutExtension(reference.Path));
                    return false;
                }
                projectReferences.Add(project);
            }
            return true;
        }

        private bool AddBinaryReferences(IEnumerable<BinaryReference> references)
        {
            return references.All(AddBinaryReferences);
        }

        public bool AddBinaryReferences(BinaryReference reference)
        {
            if (reference.Name.IsSystemAssemblyName())
            {
                return true;
            }
            if (reference.ResolvedVersion.IsNullOrEmpty())
            {
                return true;
            }
            ComponentVersion componentVersion = _localRepository.GetComponentVersion(reference.Name,
                                                                                     reference.ResolvedVersion);
            if (componentVersion == null)
            {
                return false;
            }
            while (!componentVersion.ReplaceWithVersion.IsNullOrEmpty())
            {
                reference.ResolvedVersion = componentVersion.ReplaceWithVersion;
                componentVersion = _localRepository.GetComponentVersion(reference.Name,
                                                                        reference.ResolvedVersion);
            }
            foreach (Dependency dependency in componentVersion.Dependencies)
            {
                var newReference = new BinaryReference
                                       {
                                           IsProjectReference = false,
                                           Name = dependency.Name,
                                           OriginalVersion = dependency.Version,
                                           ResolvedVersion = dependency.Version
                                       };
                reference.References.Add(newReference);
                AddBinaryReferences(newReference);
            }
            return true;
        }

        private bool VerifyAssembliesExist(ProjectFile dependencyFile, IList<BinaryReference> references)
        {
            foreach (Reference reference in dependencyFile.References)
            {
                string assemblyName = reference.AssemblyName;
                if (assemblyName.IsSystemAssemblyName())
                {
                    continue;
                }
                if (!_localRepository.Contains(assemblyName))
                {
                    OutputWriter.WriteLine(OutputVerbosity.Quiet, ResourceDispenser.GetString("RESOLVE_ASSEMBLY_NOT_FOUND"),
                                       assemblyName);
                    return false;
                }
                references.Add(new BinaryReference
                                   {
                                       IsProjectReference = true,
                                       Name = assemblyName,
                                       OriginalVersion = reference.SpecifiedVersion,
                                       ResolvedVersion = reference.SpecifiedVersion
                                   });
            }
            return true;
        }

        private bool VerifyAssemblyVersionsExist(IEnumerable<BinaryReference> references)
        {
            foreach (BinaryReference reference in references)
            {
                if (reference.Name.IsSystemAssemblyName())
                {
                    continue;
                }
                if (reference.OriginalVersion.IsNullOrEmpty())
                {
                    continue;
                }
                ComponentVersion componentVersion = _localRepository.GetComponentVersion(reference.Name,
                                                                                         reference.OriginalVersion);
                if (componentVersion == null)
                {
                    OutputWriter.WriteLine(OutputVerbosity.Quiet,
                                       ResourceDispenser.GetString("RESOLVE_ASSEMBLY_VERSION_NOT_FOUND"), reference.Name,
                                       reference.OriginalVersion);
                    return false;
                }
                if (!string.IsNullOrEmpty(componentVersion.ReplaceWithVersion))
                {
                    reference.ResolvedVersion = componentVersion.ReplaceWithVersion;
                }
            }
            return true;
        }

        static private void VerifyDependencyFilesExist(SolutionFile solutionFile)
        {
            if (solutionFile.CachedProjectFiles.Values
                .Any(projectFile => !File.Exists(projectFile.FileName + FileExtensions.DependencyFile)))
            {
                ExtractDependencyFiles(solutionFile);
            }
        }
    }
}